package it.traveldream.beans;

import it.traveldream.dto.DepartureDTO;
import it.traveldream.dto.LocationDTO;
import it.traveldream.dto.ProductDTO;
import it.traveldream.dto.ProductTypeDTO;
import it.traveldream.dto.TransportMeanDTO;
import it.traveldream.entities.Customer;
import it.traveldream.entities.Departure;
import it.traveldream.entities.Employee;
import it.traveldream.entities.Location;
import it.traveldream.entities.MeanType;
import it.traveldream.entities.Product;
import it.traveldream.entities.ProductType;
import it.traveldream.entities.TransportMean;
import it.traveldream.exceptions.InvalidProductException;
import it.traveldream.exceptions.LocationNotFoundException;
import it.traveldream.exceptions.ProductNotFoundException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Local;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * Session Bean implementation class TransportMeanManager
 */
@Stateless
@Local(TransportMeanManagerInterface.class)
@LocalBean
public class TransportMeanManager implements TransportMeanManagerInterface {

	@PersistenceContext
	private EntityManager em;
	private Logger log;
    /**
     * Default constructor. 
     */
    public TransportMeanManager() {
        super();
        this.log = Logger.getLogger(this.getClass().getSimpleName());
    }

	@Override
	@RolesAllowed(Employee.role)
	public List<ProductTypeDTO> getProductTypes() {
		@SuppressWarnings("unchecked")
		List<ProductType> types = this.em.createNamedQuery("getAllProductTypes").getResultList();
		
		ArrayList<ProductTypeDTO> typesDTO = new ArrayList<>();
		for (ProductType t : types){
			typesDTO.add(t.toDTO());
		}
		
		return typesDTO;
	}
	
	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<LocationDTO> getLocations() {
		try{
			@SuppressWarnings("unchecked")
			List<Location> locations = this.em.createNamedQuery("getAllLocations").getResultList();
			
			List<LocationDTO> dto = new ArrayList<>();
			for (Location l : locations){
				dto.add(l.toDTO());
			}
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}
	
	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<DepartureDTO> getDepartures() {
		try{
			@SuppressWarnings("unchecked")
			List<Departure> departures = this.em.createNamedQuery("getAllDepartures").getResultList();
			
			List<DepartureDTO> dto = new ArrayList<>();
			for (Departure d : departures){
				dto.add(d.toDTO());
			}
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}
	
	@Override
	@SuppressWarnings("unchecked")
	@RolesAllowed({Employee.role})
	public List<ProductDTO> getProductsByLocation(LocationDTO location)
			throws LocationNotFoundException {
		
		/* getAllProductInLocation query returns a List
		 * containing two Object for each row.
		 * row[0] : Product, row[1] : ProductType
		 */
		Location l = this.em.find(Location.class, location.getLocationId());
		if (l == null){
			this.log.severe("ERROR: Location " + location.getName() + " not found");
			throw new LocationNotFoundException();
		}
		
		List<Object> products = 
        		this.em.createNamedQuery("getAllProductInLocation").
        		setParameter("location", l).getResultList();
		ArrayList<ProductDTO> productsDTO = new ArrayList<>();
		
		Iterator<Object> i = products.iterator();
        while (i.hasNext()){
        	Object[] row = (Object[])i.next();
        	
        	Product p = (Product) row[0];
        	Long typeId = ((ProductType) row[1]).getProductTypeId();
        	String typeName = ((ProductType) row[1]).getProductTypeName();
        	
        	if (typeId.toString().equals(ProductType.TRANSPORTMEAN)){
        		TransportMean m = this.em.find(TransportMean.class, p.getProductId());
        		if (m.getMeanType().getName().contains("BACK"))
        			continue;
        	}
        	
        	ProductDTO DTO = p.toDTO();
        	ProductTypeDTO typeDTO = new ProductTypeDTO();
        	
        	typeDTO.setProductTypeId(typeId);
        	typeDTO.setName(typeName);
        	DTO.setProductType(typeDTO);
        	
        	productsDTO.add(DTO);
        }
		return productsDTO;
	}

	@Override
	@RolesAllowed({Employee.role})
	public TransportMeanDTO getProduct(ProductDTO product)
			throws ProductNotFoundException {
		
		Long productId = product.getProductId();
		TransportMean mean = this.em.find(TransportMean.class, productId);
		if (mean == null){
			this.log.severe("ERROR: Problem while fetching transport mean " + product.getName());
			throw new ProductNotFoundException();
		}
		
		return mean.toDTO();
	}

	@SuppressWarnings("unchecked")
	@Override
	@RolesAllowed({Employee.role})
	public TransportMeanDTO saveProduct(TransportMeanDTO product)
			throws InvalidProductException {
		try{
			Location l = this.em.find(Location.class, product.getLocation().getLocationId());
			Departure d = this.em.find(Departure.class, product.getDeparture().getDepartureId());

			MeanType mt = (MeanType) this.em.createNamedQuery("getMeanTypeByName").
					setParameter("name", product.getMeanType()).
					getSingleResult();
			if (mt.getName().contains("BACK"))
					throw new InvalidProductException();
			
			List<TransportMean> existing = this.em.createNamedQuery("getExistingMeanFromDepartureToLocation").
					setParameter("departure", d).
					setParameter("location", l).
					getResultList();
			if (existing.size() > 0)
				throw new InvalidProductException();
			
			List<TransportMean> back = this.em.createNamedQuery("getExistingBackMeanOfTypeToLocation").
					setParameter("location", l).
					setParameter("type", mt).getResultList();
			
			TransportMean mean = new TransportMean(product.getName(), l);
			mean.setDeparture(d);
			mean.setMeanType(mt);
			mean.setPrice(product.getPrice());
			mean.setSpaces(product.getSpaces());
			
			if (back.size() == 0){
				TransportMean backMean = new TransportMean(product.getName(), l);
				backMean.setDeparture(d);
				backMean.setPrice(mean.getPrice());
				backMean.setSpaces(mean.getSpaces());
				
				MeanType t = this.em.find(MeanType.class, Long.decode(MeanType.BACK_FLIGHT));
				backMean.setMeanType(t);
				this.em.persist(backMean);
			}
		
			this.em.persist(mean);
			this.em.flush();
			
			return mean.toDTO();
		} catch (Exception e){
			this.log.severe("ERROR: Creating transport mean: "  + e.getMessage());
			throw new InvalidProductException();
		}
	}

	@Override
	@RolesAllowed({Employee.role})
	public void updateProduct(TransportMeanDTO product)
			throws ProductNotFoundException, InvalidProductException {
		
		TransportMean mean = this.em.find(TransportMean.class, product.getProductId());
		if (mean == null){
			this.log.severe("ERROR: Product " + product.getName() + " not found");
			throw new ProductNotFoundException();
		}
		try{
			Location l = this.em.find(Location.class, product.getLocation().getLocationId());
			Departure d = this.em.find(Departure.class, product.getDeparture().getDepartureId());
			MeanType mt = (MeanType) this.em.createNamedQuery("getMeanTypeByName").
					setParameter("name", product.getMeanType()).
					getSingleResult();
			
			mean.setName(product.getName());
			mean.setLocation(l);
			mean.setMeanType(mt);
			mean.setDeparture(d);
			mean.setPrice(product.getPrice());
			mean.setSpaces(product.getSpaces());
			
			this.em.merge(mean);
			this.em.flush();
			
		}  catch (Exception e){
			this.log.severe("ERROR: Problem while updating transport mean " + product.getName());
			throw new InvalidProductException();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	@RolesAllowed({Employee.role})
	public void deleteProduct(TransportMeanDTO product)
			throws ProductNotFoundException {
		TransportMean mean = this.em.find(TransportMean.class, product.getProductId());
		
		if (mean == null){
			this.log.severe("ERROR: Product " + product.getName() + " not found");
			throw new ProductNotFoundException();
		}
		
		List<TransportMean> back = this.em.createNamedQuery("getExistingMeanFromDepartureToLocation").
				setParameter("departure", mean.getDeparture()).
				setParameter("location", mean.getLocation()).
				getResultList();
		
		try{
			if (back.size() > 0)
				this.em.remove(back.get(0));
			this.em.remove(mean);
			this.em.flush();
		} catch (Exception e){
			this.log.severe("ERROR: Problem while removing transport mean " + product.getName());
			throw new RuntimeException();
		}
		
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<TransportMeanDTO> getMeansByLocation(LocationDTO location)
			throws LocationNotFoundException {
		Location l = this.em.find(Location.class, location.getLocationId());
		if (l == null)
			throw new LocationNotFoundException();
		
		@SuppressWarnings("unchecked")
		List<TransportMean> products = 
        		this.em.createNamedQuery("getAllMeanToLocation").
        		setParameter("location", l).getResultList();
		
		ArrayList<TransportMeanDTO> DTOs = new ArrayList<TransportMeanDTO>();
		for (TransportMean m : products){
			if (! m.getMeanType().getName().contains("BACK"))
				DTOs.add(m.toDTO());
		}
		return DTOs;
	}

	@Override
	@RolesAllowed({Employee.role, Customer.role})
	public List<String> getMeanTypes() {
		try{
			@SuppressWarnings("unchecked")
			List<MeanType> types = this.em.createNamedQuery("getMeanTypes").getResultList();
			
			List<String> dto = new ArrayList<>();
			for (MeanType t : types){
				if (! t.getName().contains("BACK"))
					dto.add(t.getName());
			}
			return dto;
		} catch (Exception e){
			this.log.severe("ERROR: " + e.getMessage());
			throw new RuntimeException();
		}
	}

}
